Skip to content

Ck 7vn/split3#1133

Merged
CK-7vn merged 9 commits into
CK-7vn/total-refactorfrom
CK-7vn/split3
May 19, 2026
Merged

Ck 7vn/split3#1133
CK-7vn merged 9 commits into
CK-7vn/total-refactorfrom
CK-7vn/split3

Conversation

@CK-7vn
Copy link
Copy Markdown
Member

@CK-7vn CK-7vn commented May 15, 2026

Pre-Submission PR Checklist

  • No debug/console/fmt.Println statements
  • Unnecessary development comments removed
  • All acceptance criteria verified
  • Functions according to ticket specifications
  • Tested manually where applicable
  • Branch rebased with latest main
  • No business logic exists within the database layer

Description of the change

This is split 3 of the refactor work, primarily focusing on Class Details. Every TSX block is byte-identical, the only things that have been changed is file/function boundaries, 3 type/bug findings, and the decomposition of 2 mega-files (SessionsTab.tsx and SessionDetailSheet.tsx) into sub-components behind unchanged parents.
Shipped in 6 commits, sequenced for review:

Commit Batch What it does
9b4511c3 + 821fe8ce 1 Helper extraction: parseLocalDate, formatShortDate, buildSessionPayload
exported from session-utils.ts; toDateInput/toTimeInput + guarded formatDurationStr added to
lib/formatters.ts; ClassEvents.tsx consumes existing month helpers from helperFunctions/formatting;
StatusBadge.getStyleForStatus DRY'd.
fa78affa 2 Three real fixes: linked_override_event typed | null (was lied about with as unknown as
casts in two producer sites); VideoViewer.tsx switched to typed API.get<Video> with discriminated-union
narrowing; eslint-disable-next-line documenting that ResidentHome.tsx useEffect's narrow deps are intentional
(preventing tour-state infinite loop).
d58ddf3f 3 SessionDetailSheet.tsx 758 → 361 LOC. Extracted 4 sub-components: SessionDetailHeader,
SessionDetailClassDetails, SessionDetailStatusSection, SessionDetailActions. getStatusBadge moved into the
Header as a top-level pure function.
af161f06 4 SessionsTab.tsx 1,200 → 470 LOC. Extracted SessionRow, SessionsTabFilterBar (absorbs
StatButton + FilterButton), SessionsTabModals, SessionsTabBulkActions, and a useSessionFilters hook.
03457f09 5 class-detail/index.tsx cleanup: extracted LoadingSkeleton and ClassNotFoundCard to sibling
files; refactored the deleteBlockerReason IIFE into a module-level getDeleteBlockerReason(blockers) helper
with a typed DeleteBlockers interface.
6ba13b47 6 Per-file punch-down: buildFacilityEvent was duplicated between SessionDetailSheet.tsx and
SessionsTabModals.tsx — moved to session-utils.ts and imported in both; EnrollResidentsModal:65 `as unknown
asdouble cast replaced withresp.type === 'one'` narrowing (same pattern as the VideoViewer fix).

a09cb4a5 ("fix: routes for top content and resident dashboard") is a separate routing fix I made kind of in the spur of the moment to take screenshots for Carolina. TopContentList.tsx, AuthenticatedLayout.tsx, and the FavoriteItem inside ResidentHome.tsx.
Not part of the Split 3 batches.

@CK-7vn CK-7vn requested a review from a team as a code owner May 15, 2026 19:28
@CK-7vn CK-7vn requested review from carddev81 and removed request for a team May 15, 2026 19:28
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • New session detail UI: header, status section, class details, actions, session rows/list, modals, filter bar, bulk-actions toolbar, loading skeleton, and “Class Not Found” card.
  • Bug Fixes

    • More reliable enrollment conflict handling and stricter video API success checks.
  • Improvements

    • Streamlined internal navigation for videos/libraries, centralized date/time formatters, status-badge styling stabilizations, layout scrolling consistency, and URL pagination behavior refined.

Walkthrough

Decomposes SessionDetailSheet into reusable sub-components, refactors SessionsTab to delegate filtering and modal UI to new components, and adds infrastructure for session row rendering and filtering. Supporting changes improve content navigation, update type contracts, and enhance formatters.

Changes

Session Detail UI Decomposition and Sessions Tab Refactoring

Layer / File(s) Summary
Type contracts and session utility expansion
frontend/src/types/events.ts, frontend/src/lib/formatters.ts, frontend/src/pages/class-detail/session-utils.ts
FacilityProgramClassEvent.linked_override_event now allows null. New formatters toDateInput and toTimeInput added; formatDurationStr handles non-positive durations. Session utilities now export parseLocalDate, formatShortDate, SessionPayload, buildSessionPayload, and buildFacilityEvent for constructing display data and facility events.
Session detail sub-components
frontend/src/components/schedule/SessionDetailHeader.tsx, frontend/src/components/schedule/SessionDetailClassDetails.tsx, frontend/src/components/schedule/SessionDetailStatusSection.tsx, frontend/src/components/schedule/SessionDetailActions.tsx
Four new components extracted from SessionDetailSheet render distinct session detail sections: header displays date and status badge; class details shows class/time/room/instructor with optional strikethrough; status section conditionally renders cancellation/reschedule/attendance changes and undo actions; actions section provides conditional buttons for attendance, rescheduling, cancellation, and instructor/room changes.
SessionDetailSheet refactoring
frontend/src/components/schedule/SessionDetailSheet.tsx
Removes inline UI and local buildFacilityEvent, imports new sub-components, and composes them with proper prop wiring for flags, details, and action callbacks.
Session row and filtering infrastructure
frontend/src/pages/class-detail/SessionRow.tsx, frontend/src/pages/class-detail/useSessionFilters.ts
SessionRow renders individual session rows with status icons, time display, and conditional action areas. useSessionFilters hook computes filtered/displayed session lists by status and time filter with configurable display limits.
Sessions tab UI components
frontend/src/pages/class-detail/SessionsTabFilterBar.tsx, frontend/src/pages/class-detail/SessionsTabBulkActions.tsx, frontend/src/pages/class-detail/SessionsTabModals.tsx
SessionsTabFilterBar renders status/time filter buttons; SessionsTabBulkActions provides a fixed bottom toolbar for bulk operations; SessionsTabModals orchestrates reschedule/cancel/change-instructor/change-room modals and the session detail sheet.
SessionsTab integration
frontend/src/pages/class-detail/SessionsTab.tsx
Delegates filtering/modal/bulk-action UI to new components and useSessionFilters hook, removing inline implementations.
Class detail page components and integration
frontend/src/pages/class-detail/ClassNotFoundCard.tsx, frontend/src/pages/class-detail/LoadingSkeleton.tsx, frontend/src/pages/class-detail/index.tsx
Introduces ClassNotFoundCard and LoadingSkeleton components; updates ClassDetailPage to import and use them, introduces DeleteBlockers interface and getDeleteBlockerReason helper for deletion constraints.
Content navigation and layout improvements
frontend/src/components/dashboard/TopContentList.tsx, frontend/src/pages/student/ResidentHome.tsx, frontend/src/components/shared/StatusBadge.tsx, frontend/src/layouts/AuthenticatedLayout.tsx, frontend/src/pages/knowledge-center/VideoViewer.tsx, frontend/src/components/schedule/RescheduleSessionModal.tsx, frontend/src/pages/programs/ClassEvents.tsx, frontend/src/pages/class-detail/EnrollResidentsModal.tsx, frontend/src/pages/knowledge-center/ResidentKnowledgeCenter.tsx, frontend/src/hooks/useUrlPagination.ts
TopContentList and ResidentHome use useNavigate for video/library routing; StatusBadge uses centralized STYLE_REGISTRY lookup; AuthenticatedLayout always applies vertical scrolling; VideoViewer narrows API typing and checks resp.type; RescheduleSessionModal uses shared formatters; ClassEvents extracts month formatting helpers; EnrollResidentsModal tightens API response handling; ResidentKnowledgeCenter adds favoriting and removes ScrollArea; useUrlPagination now updates search via navigate.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Ck 7vn/split3' is a vague branch reference that does not clearly communicate the main change; scanning history would not reveal what was refactored. Use a descriptive title such as 'refactor: decompose class details page components' or 'refactor: extract session detail and session list sub-components'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description provides detailed context, commit batches, affected files, and the PR's refactoring purpose, clearly relating to the changeset content.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/pages/class-detail/index.tsx (1)

88-97: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Reuse DeleteBlockers in the SWR response type to avoid drift.

blockers is re-declared inline here even though DeleteBlockers was introduced above. Reusing the interface keeps one source of truth.

Suggested fix
     const { data: deleteCheckResp, mutate: mutateDeleteCheck } = useSWR<
         ServerResponseOne<{
             can_delete: boolean;
-            blockers: {
-                enrollments?: number;
-                completions?: number;
-                attendance_flags?: number;
-                non_deletable_status?: string;
-            };
+            blockers: DeleteBlockers;
         }>
     >(class_id ? `/api/program-classes/${class_id}/delete-check` : null);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/index.tsx` around lines 88 - 97, The SWR
response type re-declares the blockers shape instead of reusing the existing
DeleteBlockers type; update the generic passed to useSWR in the delete-check
hook so ServerResponseOne references DeleteBlockers (e.g.,
useSWR<ServerResponseOne<DeleteBlockers>> or ServerResponseOne<{ can_delete:
boolean; blockers: DeleteBlockers }>) so the deleteCheckResp / mutateDeleteCheck
types derive from the single DeleteBlockers definition used elsewhere.
frontend/src/pages/knowledge-center/VideoViewer.tsx (1)

28-35: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle rejected API calls so loading state always resolves.

If API.get throws, isLoading never flips to false, leaving the page stuck in skeleton state. Add try/catch/finally around Line 28.

Suggested fix
         const fetchVideoData = async () => {
-            const resp = await API.get<Video>(`videos/${videoId}`);
-            if (resp.success && resp.type === 'one') {
-                setIsLoading(false);
-                setVideo(resp.data);
-            } else {
-                setError(resp.message);
-                setIsLoading(false);
-            }
+            try {
+                const resp = await API.get<Video>(`videos/${videoId}`);
+                if (resp.success && resp.type === 'one') {
+                    setVideo(resp.data);
+                    setError(null);
+                } else {
+                    setError(resp.message || 'Video currently unavailable');
+                }
+            } catch {
+                setError('Video currently unavailable or unauthorized to view');
+            } finally {
+                setIsLoading(false);
+            }
         };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/knowledge-center/VideoViewer.tsx` around lines 28 - 35,
The API.get call in the VideoViewer component can throw and leave isLoading
true; wrap the call to API.get<Video>(`videos/${videoId}`) inside a
try/catch/finally block (within the async effect or handler where this code
lives) so that on success you call setVideo(resp.data) and on failure you call
setError(err?.message || resp?.message) in the catch, and always call
setIsLoading(false) in the finally; reference the VideoViewer component and the
state setters setIsLoading, setVideo, setError and the API.get invocation when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/dashboard/TopContentList.tsx`:
- Around line 25-27: Replace the interactive <div> wrapper in the TopContentList
component with a semantic <button type="button"> to restore keyboard focus and
assistive tech support: change the element that currently uses
onClick={handleClick} and the className "flex items-center gap-3 p-3 rounded-lg
border border-border bg-card hover:shadow-md transition-shadow cursor-pointer"
to a <button type="button"> keeping the same className and onClick handler
(ensure it is not nested inside another button), and remove any non-button-only
attributes if present so the element behaves like a proper button for keyboard
users.

In `@frontend/src/pages/class-detail/ClassNotFoundCard.tsx`:
- Line 9: The flex container in ClassNotFoundCard (the div with className
"bg-[`#E2E7EA`] flex items-center justify-center") lacks a defined height so
items-center has no effect; update that container to include an appropriate
height utility (e.g., h-full, min-h-screen, or a fixed height like
h-96/min-h-[200px] depending on the layout) so the content will actually be
vertically centered.

In `@frontend/src/pages/class-detail/LoadingSkeleton.tsx`:
- Around line 11-21: The skeleton grids use fixed column counts causing overflow
on small screens; update the container div classNames in LoadingSkeleton.tsx to
use responsive Tailwind grid classes (e.g., replace "grid grid-cols-5 gap-4"
with something like "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3
lg:grid-cols-5 gap-4" for the five-item row, and replace "grid grid-cols-3 gap-6
mb-6" with "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 mb-6" for the
three-item row) so the Skeleton components resize and wrap appropriately.

In `@frontend/src/pages/class-detail/session-utils.ts`:
- Around line 517-533: The current lookup uses findActiveOverride(classEvents,
session.instance.date) which matches by date only and can pick the wrong
override for sessions on the same day; update the call and matching logic to
consider the session start time as well: parse the session.instance.class_time
start (parts[0]) into a normalized time string or minutes and pass it into
findActiveOverride (e.g., findActiveOverride(classEvents, session.instance.date,
startTime)), then change findActiveOverride to require both date and start-time
equality when selecting an active override so room_id/instructor_id/override_id
resolution uses date+start-time instead of date-only.

In `@frontend/src/pages/class-detail/SessionRow.tsx`:
- Around line 107-110: The clickable row in SessionRow.tsx currently only uses
onClick and cursor-pointer, making it inaccessible to keyboard users; update the
outer div (the element with onClick and className that includes
getBorderClass()) to be focusable and announced as a button by adding
role="button" and tabIndex={0}, and add an onKeyDown handler that invokes the
same onClick callback when Enter or Space is pressed (ensure it prevents default
for Space so the page doesn't scroll). If the row can be disabled, mirror that
state with aria-disabled accordingly.

In `@frontend/src/pages/class-detail/useSessionFilters.ts`:
- Around line 80-84: The block handling statusFilter === 'upcoming' performs an
unnecessary reversal (result = [...result].reverse()) that is later undone by
the chronological sort in the upcomingSessions memo; remove that reverse line so
the filtered array remains in its natural order (keep the filter using
isUpcoming, isCancelled, and isRescheduledFrom) and ensure any length checks
that use filtered continue to work unchanged.

In `@frontend/src/pages/student/ResidentHome.tsx`:
- Around line 101-104: Replace the non-semantic, non-keyboard-operable div
wrapper with a semantic <button type="button"> that preserves the existing
onClick={handleClick} and className (so existing styles and handlers on the
element remain intact); ensure you remove any redundant role or tabIndex
previously added to the div, confirm there are no nested interactive controls
inside the button (or move them out) and if needed add an aria-label prop for
accessibility—locate the wrapper around the favorite item (the element using
handleClick in ResidentHome.tsx / FavoriteItem) and perform this replacement.

---

Outside diff comments:
In `@frontend/src/pages/class-detail/index.tsx`:
- Around line 88-97: The SWR response type re-declares the blockers shape
instead of reusing the existing DeleteBlockers type; update the generic passed
to useSWR in the delete-check hook so ServerResponseOne references
DeleteBlockers (e.g., useSWR<ServerResponseOne<DeleteBlockers>> or
ServerResponseOne<{ can_delete: boolean; blockers: DeleteBlockers }>) so the
deleteCheckResp / mutateDeleteCheck types derive from the single DeleteBlockers
definition used elsewhere.

In `@frontend/src/pages/knowledge-center/VideoViewer.tsx`:
- Around line 28-35: The API.get call in the VideoViewer component can throw and
leave isLoading true; wrap the call to API.get<Video>(`videos/${videoId}`)
inside a try/catch/finally block (within the async effect or handler where this
code lives) so that on success you call setVideo(resp.data) and on failure you
call setError(err?.message || resp?.message) in the catch, and always call
setIsLoading(false) in the finally; reference the VideoViewer component and the
state setters setIsLoading, setVideo, setError and the API.get invocation when
making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c561dcac-acf6-4653-b9fc-fd70ba888551

📥 Commits

Reviewing files that changed from the base of the PR and between 96c36e4 and a09cb4a.

📒 Files selected for processing (25)
  • frontend/src/components/dashboard/TopContentList.tsx
  • frontend/src/components/schedule/RescheduleSessionModal.tsx
  • frontend/src/components/schedule/SessionDetailActions.tsx
  • frontend/src/components/schedule/SessionDetailClassDetails.tsx
  • frontend/src/components/schedule/SessionDetailHeader.tsx
  • frontend/src/components/schedule/SessionDetailSheet.tsx
  • frontend/src/components/schedule/SessionDetailStatusSection.tsx
  • frontend/src/components/shared/StatusBadge.tsx
  • frontend/src/layouts/AuthenticatedLayout.tsx
  • frontend/src/lib/formatters.ts
  • frontend/src/pages/class-detail/ClassNotFoundCard.tsx
  • frontend/src/pages/class-detail/EnrollResidentsModal.tsx
  • frontend/src/pages/class-detail/LoadingSkeleton.tsx
  • frontend/src/pages/class-detail/SessionRow.tsx
  • frontend/src/pages/class-detail/SessionsTab.tsx
  • frontend/src/pages/class-detail/SessionsTabBulkActions.tsx
  • frontend/src/pages/class-detail/SessionsTabFilterBar.tsx
  • frontend/src/pages/class-detail/SessionsTabModals.tsx
  • frontend/src/pages/class-detail/index.tsx
  • frontend/src/pages/class-detail/session-utils.ts
  • frontend/src/pages/class-detail/useSessionFilters.ts
  • frontend/src/pages/knowledge-center/VideoViewer.tsx
  • frontend/src/pages/programs/ClassEvents.tsx
  • frontend/src/pages/student/ResidentHome.tsx
  • frontend/src/types/events.ts

Comment on lines +25 to +27
<div
onClick={handleClick}
className="flex items-center gap-3 p-3 rounded-lg border border-border bg-card hover:shadow-md transition-shadow cursor-pointer"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find frontend -name "TopContentList.tsx" -type f

Repository: UnlockedLabs/UnlockEdv2

Length of output: 120


🏁 Script executed:

cat -n frontend/src/components/dashboard/TopContentList.tsx

Repository: UnlockedLabs/UnlockEdv2

Length of output: 3262


🏁 Script executed:

rg "onClick.*handleClick" frontend/src/components/dashboard/ -A 3 -B 1

Repository: UnlockedLabs/UnlockEdv2

Length of output: 566


🏁 Script executed:

rg "<div[^>]*onClick" frontend/src/components/ --type tsx --type ts -c

Repository: UnlockedLabs/UnlockEdv2

Length of output: 95


🏁 Script executed:

rg "<div[^>]*onClick" frontend/src/components/ -c

Repository: UnlockedLabs/UnlockEdv2

Length of output: 49


🏁 Script executed:

rg "onKeyDown|onKeyPress|onKeyUp" frontend/src/components/dashboard/TopContentList.tsx

Repository: UnlockedLabs/UnlockEdv2

Length of output: 49


Replace interactive div with semantic button element for keyboard accessibility.

Line 25 uses a non-semantic <div> for interactive content, which is not keyboard-focusable by default and blocks keyboard users from accessing this core navigation feature. Since the component already uses semantic buttons elsewhere (line 71), replace this div with a <button type="button"> element to provide proper keyboard and assistive technology support.

Suggested fix
-        <div
+        <button
+            type="button"
             onClick={handleClick}
-            className="flex items-center gap-3 p-3 rounded-lg border border-border bg-card hover:shadow-md transition-shadow cursor-pointer"
+            className="w-full text-left flex items-center gap-3 p-3 rounded-lg border border-border bg-card hover:shadow-md transition-shadow cursor-pointer"
         >
@@
-        </div>
+        </button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/dashboard/TopContentList.tsx` around lines 25 - 27,
Replace the interactive <div> wrapper in the TopContentList component with a
semantic <button type="button"> to restore keyboard focus and assistive tech
support: change the element that currently uses onClick={handleClick} and the
className "flex items-center gap-3 p-3 rounded-lg border border-border bg-card
hover:shadow-md transition-shadow cursor-pointer" to a <button type="button">
keeping the same className and onClick handler (ensure it is not nested inside
another button), and remove any non-button-only attributes if present so the
element behaves like a proper button for keyboard users.


export function ClassNotFoundCard({ onBack }: ClassNotFoundCardProps) {
return (
<div className="bg-[#E2E7EA] flex items-center justify-center">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add container height for actual vertical centering.

items-center won’t vertically center here without a defined height on the flex container, so the card will sit at the top instead of centered.

Suggested fix
-        <div className="bg-[`#E2E7EA`] flex items-center justify-center">
+        <div className="bg-[`#E2E7EA`] min-h-screen flex items-center justify-center">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="bg-[#E2E7EA] flex items-center justify-center">
<div className="bg-[`#E2E7EA`] min-h-screen flex items-center justify-center">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/ClassNotFoundCard.tsx` at line 9, The flex
container in ClassNotFoundCard (the div with className "bg-[`#E2E7EA`] flex
items-center justify-center") lacks a defined height so items-center has no
effect; update that container to include an appropriate height utility (e.g.,
h-full, min-h-screen, or a fixed height like h-96/min-h-[200px] depending on the
layout) so the content will actually be vertically centered.

Comment on lines +11 to +21
<div className="grid grid-cols-5 gap-4">
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="h-20 rounded-lg" />
))}
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-6 py-6">
<div className="grid grid-cols-3 gap-6 mb-6">
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className="h-40 rounded-lg" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use responsive grid columns for small screens.

The fixed 5/3-column skeleton grids can break on narrow widths. Making these responsive avoids layout overflow in loading state.

Suggested fix
-                    <div className="grid grid-cols-5 gap-4">
+                    <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
...
-                <div className="grid grid-cols-3 gap-6 mb-6">
+                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="grid grid-cols-5 gap-4">
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="h-20 rounded-lg" />
))}
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-6 py-6">
<div className="grid grid-cols-3 gap-6 mb-6">
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className="h-40 rounded-lg" />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="h-20 rounded-lg" />
))}
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-6 py-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className="h-40 rounded-lg" />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/LoadingSkeleton.tsx` around lines 11 - 21,
The skeleton grids use fixed column counts causing overflow on small screens;
update the container div classNames in LoadingSkeleton.tsx to use responsive
Tailwind grid classes (e.g., replace "grid grid-cols-5 gap-4" with something
like "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4" for
the five-item row, and replace "grid grid-cols-3 gap-6 mb-6" with "grid
grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 mb-6" for the three-item row) so
the Skeleton components resize and wrap appropriately.

Comment on lines +517 to +533
const activeOverride = findActiveOverride(classEvents, session.instance.date);
const parts = session.instance.class_time.split('-');
const [sh = 0, sm = 0] = (parts[0] ?? '').split(':').map(Number);
const [eh = 0, em = 0] = (parts[1] ?? '').split(':').map(Number);
const start = new Date(session.dateObj);
start.setHours(sh, sm, 0, 0);
const end = new Date(session.dateObj);
end.setHours(eh, em, 0, 0);
return {
id: eventId,
class_id: classId,
duration: backingEvent?.duration ?? '',
room_id: activeOverride?.room_id ?? backingEvent?.room_id ?? 0,
recurrence_rule: backingEvent?.recurrence_rule ?? '',
is_cancelled: session.instance.is_cancelled,
instructor_id: activeOverride?.instructor_id ?? backingEvent?.instructor_id ?? null,
overrides: backingEvent?.overrides ?? [],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Match overrides by date and start time when building the facility event.

Line 517 uses a date-only lookup, so sessions sharing the same date can resolve to the wrong override (room/instructor/override_id). This can drive incorrect modal payloads for cancel/change actions.

Suggested fix
-    const activeOverride = findActiveOverride(classEvents, session.instance.date);
+    const sessionStart = (session.instance.class_time.split('-')[0] ?? '').trim();
+    const activeOverride = classEvents
+        .flatMap((e) => e.overrides ?? [])
+        .find((ov) => {
+            if (ov.is_cancelled) return false;
+            const ovDate = parseOverrideDate(ov.override_rrule);
+            if (ovDate !== session.instance.date) return false;
+            const ovStart = parseOverrideStartTime(ov.override_rrule);
+            return !ovStart || ovStart === sessionStart;
+        });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/session-utils.ts` around lines 517 - 533, The
current lookup uses findActiveOverride(classEvents, session.instance.date) which
matches by date only and can pick the wrong override for sessions on the same
day; update the call and matching logic to consider the session start time as
well: parse the session.instance.class_time start (parts[0]) into a normalized
time string or minutes and pass it into findActiveOverride (e.g.,
findActiveOverride(classEvents, session.instance.date, startTime)), then change
findActiveOverride to require both date and start-time equality when selecting
an active override so room_id/instructor_id/override_id resolution uses
date+start-time instead of date-only.

Comment on lines +107 to +110
<div
onClick={onClick}
className={`flex items-center justify-between p-4 rounded-lg border transition-colors cursor-pointer ${getBorderClass()}`}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider improving keyboard accessibility for the clickable row.

The row div uses onClick and cursor-pointer but lacks a role="button" attribute and keyboard event handler (onKeyDown). While the row contains interactive child elements, keyboard-only users cannot trigger the row's onClick action directly.

♿ Proposed accessibility enhancement
 <div
     onClick={onClick}
+    role="button"
+    tabIndex={0}
+    onKeyDown={(e) => {
+        if (e.key === 'Enter' || e.key === ' ') {
+            e.preventDefault();
+            onClick();
+        }
+    }}
     className={`flex items-center justify-between p-4 rounded-lg border transition-colors cursor-pointer ${getBorderClass()}`}
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
onClick={onClick}
className={`flex items-center justify-between p-4 rounded-lg border transition-colors cursor-pointer ${getBorderClass()}`}
>
<div
onClick={onClick}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
}}
className={`flex items-center justify-between p-4 rounded-lg border transition-colors cursor-pointer ${getBorderClass()}`}
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/SessionRow.tsx` around lines 107 - 110, The
clickable row in SessionRow.tsx currently only uses onClick and cursor-pointer,
making it inaccessible to keyboard users; update the outer div (the element with
onClick and className that includes getBorderClass()) to be focusable and
announced as a button by adding role="button" and tabIndex={0}, and add an
onKeyDown handler that invokes the same onClick callback when Enter or Space is
pressed (ensure it prevents default for Space so the page doesn't scroll). If
the row can be disabled, mirror that state with aria-disabled accordingly.

Comment on lines +80 to +84
} else if (statusFilter === 'upcoming') {
result = result.filter(
(s) => s.isUpcoming && !s.isCancelled && !s.isRescheduledFrom
);
result = [...result].reverse();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Redundant reverse operation.

Line 84 reverses the upcoming filter results, but line 106 later sorts upcomingSessions chronologically, effectively undoing the reverse. Since filtered is only used for length checks (not direct iteration), the reverse appears unnecessary.

🔧 Simplification suggestion
     } else if (statusFilter === 'upcoming') {
         result = result.filter(
             (s) => s.isUpcoming && !s.isCancelled && !s.isRescheduledFrom
         );
-        result = [...result].reverse();
     } else if (statusFilter === 'cancelled') {

The upcomingSessions memo already handles sorting, so this reverse is redundant.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (statusFilter === 'upcoming') {
result = result.filter(
(s) => s.isUpcoming && !s.isCancelled && !s.isRescheduledFrom
);
result = [...result].reverse();
} else if (statusFilter === 'upcoming') {
result = result.filter(
(s) => s.isUpcoming && !s.isCancelled && !s.isRescheduledFrom
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/useSessionFilters.ts` around lines 80 - 84,
The block handling statusFilter === 'upcoming' performs an unnecessary reversal
(result = [...result].reverse()) that is later undone by the chronological sort
in the upcomingSessions memo; remove that reverse line so the filtered array
remains in its natural order (keep the filter using isUpcoming, isCancelled, and
isRescheduledFrom) and ensure any length checks that use filtered continue to
work unchanged.

Comment on lines +101 to 104
<div
onClick={handleClick}
className="flex items-center gap-3 p-3 rounded-lg border border-border hover:shadow-md transition-shadow cursor-pointer"
>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get an overview of its structure
cd frontend/src/pages/student && wc -l ResidentHome.tsx

Repository: UnlockedLabs/UnlockEdv2

Length of output: 88


🏁 Script executed:

# Read the specific lines mentioned (101-104) with context
sed -n '85,120p' frontend/src/pages/student/ResidentHome.tsx

Repository: UnlockedLabs/UnlockEdv2

Length of output: 1240


🏁 Script executed:

# Look for the complete component structure and any accessibility attributes
sed -n '1,150p' frontend/src/pages/student/ResidentHome.tsx | grep -n -i "div\|button\|aria\|role\|onClick" | head -20

Repository: UnlockedLabs/UnlockEdv2

Length of output: 615


Replace clickable <div> with <button> for keyboard accessibility.

The FavoriteItem component uses a <div> with onClick handler that is not keyboard-operable. Users cannot access this favorite item using Tab and Enter/Space keys, which violates WCAG accessibility standards. Replace the <div> with a semantic <button type="button"> element.

Suggested fix
-        <div
+        <button
+            type="button"
             onClick={handleClick}
-            className="flex items-center gap-3 p-3 rounded-lg border border-border hover:shadow-md transition-shadow cursor-pointer"
+            className="w-full text-left flex items-center gap-3 p-3 rounded-lg border border-border hover:shadow-md transition-shadow cursor-pointer"
         >
@@
-        </div>
+        </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
onClick={handleClick}
className="flex items-center gap-3 p-3 rounded-lg border border-border hover:shadow-md transition-shadow cursor-pointer"
>
<button
type="button"
onClick={handleClick}
className="w-full text-left flex items-center gap-3 p-3 rounded-lg border border-border hover:shadow-md transition-shadow cursor-pointer"
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/student/ResidentHome.tsx` around lines 101 - 104, Replace
the non-semantic, non-keyboard-operable div wrapper with a semantic <button
type="button"> that preserves the existing onClick={handleClick} and className
(so existing styles and handlers on the element remain intact); ensure you
remove any redundant role or tabIndex previously added to the div, confirm there
are no nested interactive controls inside the button (or move them out) and if
needed add an aria-label prop for accessibility—locate the wrapper around the
favorite item (the element using handleClick in ResidentHome.tsx / FavoriteItem)
and perform this replacement.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:'( Oh no..this is one of the instances I've been talking about for weeks!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CK-7vn Sorry for piling on with a known pain point! 😅 It sounds like the <div onClick><button> accessibility refactor is something that's been on the radar for a while and spans multiple places in the codebase.

Would you like me to open a GitHub issue to formally track this pattern? I can scan the codebase for all similar <div onClick> / non-semantic clickable elements and capture them in one place so it's easier to address holistically in a dedicated PR rather than as a series of one-off review comments.

Just say the word and I'll create the issue and assign it to you!

@CK-7vn
Copy link
Copy Markdown
Member Author

CK-7vn commented May 15, 2026

I've went over all of code-rabbits findings, and although correct, I would argue against implementing them in this PR as this is simply a byte to byte refactoring and not trying to fix issues like what was noted. Worth making tickets for the majority of them though.

@carddev81
Copy link
Copy Markdown
Contributor

I've went over all of code-rabbits findings, and although correct, I would argue against implementing them in this PR as this is simply a byte to byte refactoring and not trying to fix issues like what was noted. Worth making tickets for the majority of them though.

@CK-7vn Sounds good. Could you please make tickets for the ones you think we should correct. I will proceed to with reviewing changes and testing functionality.

@carddev81
Copy link
Copy Markdown
Contributor

@CK-7vn no telling what caused this, but the videoviewer expands off of the page now, should be an easy fix:

image

@carddev81
Copy link
Copy Markdown
Contributor

@CK-7vn There are no scrollbars on the Knowledge Center page (Residents View)

image

Copy link
Copy Markdown
Contributor

@carddev81 carddev81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some functionality issues. Looks great. Good job

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/class-detail/EnrollResidentsModal.tsx (1)

64-70: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear conflictMap when conflict fetch is not a valid one response.

As written, Line 64 only updates state on a successful one payload. On failure/non-one, old conflicts can remain visible from a prior fetch in the same modal session.

Suggested fix
         }).then((resp) => {
             if (resp.success && resp.type === 'one') {
                 const map = new Map<number, ConflictDetail>();
                 for (const c of resp.data.conflicts ?? []) {
                     map.set(c.user_id, c);
                 }
                 setConflictMap(map);
+            } else {
+                setConflictMap(new Map());
             }
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/EnrollResidentsModal.tsx` around lines 64 -
70, When handling the fetch response in EnrollResidentsModal (the resp handling
block that currently only updates on resp.success && resp.type === 'one'),
ensure you clear stale conflicts when the response is not a valid "one" payload
by calling setConflictMap(new Map()) (or an empty value consistent with the
ConflictMap type) in the else path; i.e., after the existing for-loop that
builds the Map for resp.data.conflicts, add an else branch that calls
setConflictMap(new Map()) so old conflicts are removed when resp.success is
false or resp.type !== 'one'.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/layouts/AuthenticatedLayout.tsx`:
- Line 106: Remove the now-unused local variable isResidentKnowledgeCenter from
AuthenticatedLayout (the dead declaration added earlier) to fix the TS6133
error; locate the declaration in AuthenticatedLayout.tsx (near where
contentClass is defined) and delete it, and then run a quick search for any
remaining references to isResidentKnowledgeCenter to ensure nothing else depends
on it before committing.

In `@frontend/src/lib/formatters.ts`:
- Around line 242-247: In formatDurationStr, guard against malformed time inputs
by validating the numeric results from timeToMinutes before computing duration:
call timeToMinutes(startTime) and timeToMinutes(endTime), ensure both are finite
numbers (e.g., using Number.isFinite), and if either is not finite or end <=
start return '0h0m0s'; otherwise compute totalMin, hours, minutes and format as
before. Reference: formatDurationStr and timeToMinutes.

In `@frontend/src/pages/class-detail/SessionRow.tsx`:
- Around line 58-60: The same-date reschedule flag is computed
(isSameDateReschedule) but not honored because later conditional checks still
prioritize isRescheduledFrom/To; update the rendering and state precedence in
SessionRow so any conditional branches that check isRescheduledFrom or
isRescheduledTo (including the blocks around the occurrences you noted: the
conditionals at lines handling 63-70, 79-87, 104, 140-148, 173-179) first test
isSameDateReschedule and treat it as a "to" row (apply the blue/to-row styling,
single undo control, and avoid from-row dashed/strike-through UI) before falling
back to isRescheduledFrom/isRescheduledTo; ensure the same precedence is applied
wherever isRescheduledFrom/isRescheduledTo affect className, displayed text, or
action buttons so same-date reschedules consistently render as the "to" state.

In `@frontend/src/pages/class-detail/SessionsTab.tsx`:
- Around line 443-445: SelectedCount currently uses selectedDates.size but bulk
actions operate on selectedUpcomingSessions, causing the toolbar/count to be
wrong after mutations; fix by computing an actionable selection count (e.g.,
const actionableSelectionCount = selectedUpcomingSessions instanceof Set ?
selectedUpcomingSessions.size : new Set([...selectedDates].filter(id =>
selectedUpcomingSessions.has ? selectedUpcomingSessions.has(id) :
selectedUpcomingSessions.includes(id))).size) and pass actionableSelectionCount
to SessionsTabBulkActions as selectedCount, and keep onClearSelection clearing
selection state via setSelectedDates(new Set()) (and any other selection state)
so the UI and actionable set stay in sync; reference SessionsTabBulkActions,
selectedDates, selectedUpcomingSessions, and setSelectedDates.

In `@frontend/src/pages/knowledge-center/ResidentKnowledgeCenter.tsx`:
- Around line 337-338: The new wrapper for ResidentKnowledgeCenter removed
explicit scrolling which caused the missing scrollbar; update the element with
id "knowledge-center-landing" (or the surrounding wrapper used in the
ResidentKnowledgeCenter component) to restore vertical scrolling by adding a
vertical scroll container (e.g., add className/props such as "overflow-y-auto"
and a constrained height like "h-full" or style={{overflowY: 'auto', height:
'100%'}}) so the page regains scrollbars; apply the same change to the other
wrapper instance referenced in the file.

---

Outside diff comments:
In `@frontend/src/pages/class-detail/EnrollResidentsModal.tsx`:
- Around line 64-70: When handling the fetch response in EnrollResidentsModal
(the resp handling block that currently only updates on resp.success &&
resp.type === 'one'), ensure you clear stale conflicts when the response is not
a valid "one" payload by calling setConflictMap(new Map()) (or an empty value
consistent with the ConflictMap type) in the else path; i.e., after the existing
for-loop that builds the Map for resp.data.conflicts, add an else branch that
calls setConflictMap(new Map()) so old conflicts are removed when resp.success
is false or resp.type !== 'one'.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fe9f9bad-a5a9-42c1-8e92-5b8c270aaf7f

📥 Commits

Reviewing files that changed from the base of the PR and between a09cb4a and 4cff97f.

📒 Files selected for processing (26)
  • frontend/src/components/dashboard/TopContentList.tsx
  • frontend/src/components/schedule/RescheduleSessionModal.tsx
  • frontend/src/components/schedule/SessionDetailActions.tsx
  • frontend/src/components/schedule/SessionDetailClassDetails.tsx
  • frontend/src/components/schedule/SessionDetailHeader.tsx
  • frontend/src/components/schedule/SessionDetailSheet.tsx
  • frontend/src/components/schedule/SessionDetailStatusSection.tsx
  • frontend/src/components/shared/StatusBadge.tsx
  • frontend/src/layouts/AuthenticatedLayout.tsx
  • frontend/src/lib/formatters.ts
  • frontend/src/pages/class-detail/ClassNotFoundCard.tsx
  • frontend/src/pages/class-detail/EnrollResidentsModal.tsx
  • frontend/src/pages/class-detail/LoadingSkeleton.tsx
  • frontend/src/pages/class-detail/SessionRow.tsx
  • frontend/src/pages/class-detail/SessionsTab.tsx
  • frontend/src/pages/class-detail/SessionsTabBulkActions.tsx
  • frontend/src/pages/class-detail/SessionsTabFilterBar.tsx
  • frontend/src/pages/class-detail/SessionsTabModals.tsx
  • frontend/src/pages/class-detail/index.tsx
  • frontend/src/pages/class-detail/session-utils.ts
  • frontend/src/pages/class-detail/useSessionFilters.ts
  • frontend/src/pages/knowledge-center/ResidentKnowledgeCenter.tsx
  • frontend/src/pages/knowledge-center/VideoViewer.tsx
  • frontend/src/pages/programs/ClassEvents.tsx
  • frontend/src/pages/student/ResidentHome.tsx
  • frontend/src/types/events.ts

Comment thread frontend/src/layouts/AuthenticatedLayout.tsx
Comment thread frontend/src/lib/formatters.ts
Comment thread frontend/src/pages/class-detail/SessionRow.tsx Outdated
Comment thread frontend/src/pages/class-detail/SessionsTab.tsx
Comment on lines +337 to +338
<div id="knowledge-center-landing">
<div className="max-w-7xl mx-auto px-6 py-8">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scroll container behavior regressed in resident knowledge center layout.

The wrapper replacement dropped explicit scroll behavior; this aligns with the reported “no scrollbars” regression for residents. Add a vertical scroll container on the new wrapper (or restore the previous scroll wrapper behavior).

Proposed fix
-        <div id="knowledge-center-landing">
-            <div className="max-w-7xl mx-auto px-6 py-8">
+        <div id="knowledge-center-landing" className="h-full overflow-y-auto">
+            <div className="max-w-7xl mx-auto px-6 py-8">

Also applies to: 478-479

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/knowledge-center/ResidentKnowledgeCenter.tsx` around lines
337 - 338, The new wrapper for ResidentKnowledgeCenter removed explicit
scrolling which caused the missing scrollbar; update the element with id
"knowledge-center-landing" (or the surrounding wrapper used in the
ResidentKnowledgeCenter component) to restore vertical scrolling by adding a
vertical scroll container (e.g., add className/props such as "overflow-y-auto"
and a constrained height like "h-full" or style={{overflowY: 'auto', height:
'100%'}}) so the page regains scrollbars; apply the same change to the other
wrapper instance referenced in the file.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
frontend/src/pages/class-detail/SessionRow.tsx (2)

245-267: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply same-date reschedule precedence to action controls.

isSameDateReschedule is computed, but actions still branch on raw reschedule flags, so same-date rows can show the “from” undo path.

Proposed fix
-                {isRescheduledFrom && isUpcoming && (
+                {treatAsFrom && isUpcoming && (
                     <Button
                         variant="outline"
                         size="sm"
                         onClick={() => onUndo()}
                         className="border-amber-300 hover:bg-amber-50"
                     >
                         <Undo2 className="size-4 mr-1.5" />
                         Undo
                     </Button>
                 )}
+                {isSameDateReschedule && isUpcoming && (
+                    <Button
+                        variant="outline"
+                        size="sm"
+                        onClick={() => onUndo()}
+                        className="border-gray-300 hover:bg-gray-50"
+                        title="Undo reschedule"
+                    >
+                        <Undo2 className="size-4 mr-1.5" />
+                        Undo
+                    </Button>
+                )}
-                {isRescheduledTo && isUpcoming && !isSameDateReschedule && (
+                {isRescheduledTo && isUpcoming && !isSameDateReschedule && (
                     <>
                         <Button
                             variant="outline"
                             size="sm"
                             onClick={() => onReschedule()}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/SessionRow.tsx` around lines 245 - 267, The
action buttons currently check raw flags (isRescheduledFrom,
isCancelledReschedule, isRescheduledTo) and ignore isSameDateReschedule, so
same-date rescheduled rows still show the "from" undo path; update the JSX
conditional logic to give isSameDateReschedule precedence by adding
!isSameDateReschedule to the relevant branches (wrap the isRescheduledFrom and
isCancelledReschedule conditions with isUpcoming && !isSameDateReschedule) and
ensure any other action branches that should be suppressed for same-date
reschedules also include that check; locate these conditions in SessionRow.tsx
around the Button rendering (symbols: isSameDateReschedule, isRescheduledFrom,
isCancelledReschedule, isRescheduledTo, isUpcoming, onUndo, onUndoCancel) and
modify them accordingly.

108-111: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the clickable row keyboard-accessible.

The row click target is mouse-only right now, so keyboard users can’t open session details from it.

Proposed fix
         <div
             onClick={onClick}
+            role="button"
+            tabIndex={0}
+            onKeyDown={(e) => {
+                if (e.key === 'Enter' || e.key === ' ') {
+                    e.preventDefault();
+                    onClick();
+                }
+            }}
             className={`flex items-center justify-between p-4 rounded-lg border transition-colors cursor-pointer ${getBorderClass()}`}
         >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/class-detail/SessionRow.tsx` around lines 108 - 111, The
div used in the SessionRow component is only clickable by mouse; make it
keyboard-accessible by adding role="button" and tabIndex={0} to the outer div,
and implement an onKeyDown handler (in the same component) that calls the
existing onClick when the user presses Enter or Space; keep using
getBorderClass() for styling and ensure any aria-label or aria-expanded is set
elsewhere if needed.
frontend/src/pages/knowledge-center/ResidentKnowledgeCenter.tsx (1)

419-420: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore vertical scrolling on the landing container.

Line 419 still drops explicit scroll behavior, which matches the reported “no scrollbars” regression in Residents view.

Proposed fix
-        <div id="knowledge-center-landing">
+        <div id="knowledge-center-landing" className="h-full overflow-y-auto">
             <div className="max-w-7xl mx-auto px-6 py-8">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/knowledge-center/ResidentKnowledgeCenter.tsx` around lines
419 - 420, The landing container with id "knowledge-center-landing" lost its
vertical scrolling; restore scroll behavior by adding an explicit vertical
overflow (e.g., apply CSS overflow-y: auto or Tailwind class "overflow-y-auto")
to the outer div (the element with id "knowledge-center-landing") so the
Residents view shows scrollbars again; update either the JSX for that div or the
associated CSS rule used by that id.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@frontend/src/pages/class-detail/SessionRow.tsx`:
- Around line 245-267: The action buttons currently check raw flags
(isRescheduledFrom, isCancelledReschedule, isRescheduledTo) and ignore
isSameDateReschedule, so same-date rescheduled rows still show the "from" undo
path; update the JSX conditional logic to give isSameDateReschedule precedence
by adding !isSameDateReschedule to the relevant branches (wrap the
isRescheduledFrom and isCancelledReschedule conditions with isUpcoming &&
!isSameDateReschedule) and ensure any other action branches that should be
suppressed for same-date reschedules also include that check; locate these
conditions in SessionRow.tsx around the Button rendering (symbols:
isSameDateReschedule, isRescheduledFrom, isCancelledReschedule, isRescheduledTo,
isUpcoming, onUndo, onUndoCancel) and modify them accordingly.
- Around line 108-111: The div used in the SessionRow component is only
clickable by mouse; make it keyboard-accessible by adding role="button" and
tabIndex={0} to the outer div, and implement an onKeyDown handler (in the same
component) that calls the existing onClick when the user presses Enter or Space;
keep using getBorderClass() for styling and ensure any aria-label or
aria-expanded is set elsewhere if needed.

In `@frontend/src/pages/knowledge-center/ResidentKnowledgeCenter.tsx`:
- Around line 419-420: The landing container with id "knowledge-center-landing"
lost its vertical scrolling; restore scroll behavior by adding an explicit
vertical overflow (e.g., apply CSS overflow-y: auto or Tailwind class
"overflow-y-auto") to the outer div (the element with id
"knowledge-center-landing") so the Residents view shows scrollbars again; update
either the JSX for that div or the associated CSS rule used by that id.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cf08f0af-b748-40c0-96ac-c9bae957c736

📥 Commits

Reviewing files that changed from the base of the PR and between 5a45736 and 26238d7.

📒 Files selected for processing (7)
  • frontend/src/hooks/useUrlPagination.ts
  • frontend/src/layouts/AuthenticatedLayout.tsx
  • frontend/src/lib/formatters.ts
  • frontend/src/pages/class-detail/SessionRow.tsx
  • frontend/src/pages/class-detail/SessionsTab.tsx
  • frontend/src/pages/knowledge-center/ResidentKnowledgeCenter.tsx
  • frontend/src/pages/programs/ClassEvents.tsx

@CK-7vn CK-7vn merged commit ba67638 into CK-7vn/total-refactor May 19, 2026
9 checks passed
@CK-7vn CK-7vn deleted the CK-7vn/split3 branch May 19, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants